﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Web.UI.HtmlControls;
using uk.ac.soton.ses.DAL;
using System.Web;
using uk.ac.soton.ses.WebControls;
using System.Security.Principal;
using uk.ac.soton.ses.UserControls;

namespace uk.ac.soton.ses.layouts
{
    public partial class FileGraphXY : LayoutsPageBase
    {
        //Data file data
        private Dictionary<int, DelimitedDataFileInfo> dataFilesDictionary = new Dictionary<int, DelimitedDataFileInfo>();

        //Field selection data
        List<XYGraphFieldSelection> fieldSelectionList = new List<XYGraphFieldSelection>();

        //Page variables
        private bool IsInitialised = false;
        private bool redirect = false;
        MaterialsMetadataContext dbcontext = null;
        private ExperimentMultipleSelection _experimentSelectionControl = null;

        //Useful properties
        private IEnumerable<DelimitedDataFileInfo> LoadedDataFiles { get { return this.dataFilesDictionary.Values.Where(df => df.HasParseAttributes); } }
        private IEnumerable<DelimitedDataFileInfo> GraphedDataFiles { get { return this.LoadedDataFiles.Where(df => df.HasRequestedFields); } }

        #region Page Life Cycle

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            try
            {   //Load a database context
                this.dbcontext = HttpContext.Current.GetMaterialsDBContext();

                //Find the experiments quick search box, cast it, and store it in a field
                this._experimentSelectionControl = this.ctl_ExperimentMultipleSelection as ExperimentMultipleSelection;
                
                //Set up events we use
                this._experimentSelectionControl.ExperimentSelectionChanged += new ExperimentSelectionChangedEventHandler(this.ExperimentSelectionChanged);
                this.ListView_DataFiles.ItemDataBound += new EventHandler<ListViewItemEventArgs>(ListView_DataFiles_ItemDataBound);
                
                //Let OnLoad do its stuff
                this.IsInitialised = true;
            }
            catch (Exception)
            {
                HDCSharedWebFunctions.AddErrorLabel("Could not load data", this.PlaceHolder_Messages);
                this.IsInitialised = false;
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (this.IsInitialised)
            {
                //Load data files from experiments user has requested
                this.LoadDataFilesFromRequestedExperiments();

                //Find out which data files user has requested
                this.ProcessDataFileQueryStrings();

                if (!this.IsPostBack)
                {
                    this.DatabindControls();
                    this.CreateGraph();
                }
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (redirect)
            {
                this.RedirectToNewUrl();
            }
        }

        public override void Dispose()
        {
            base.Dispose();
            if (this.dbcontext != null)
            {
                this.dbcontext.Connection.Close();
                this.dbcontext.Dispose();
            }
        }
        
        #endregion Page Life Cycle


        #region Private methods

        private void ProcessDataFileQueryStrings()
        {
            //Get parameters
            string[] startRowStrings = Page.Request.QueryString.GetValues("StartRow") ?? new string[0];
            string[] maxRowsString = Page.Request.QueryString.GetValues("MaxRows") ?? new string[0];
            string[] delimiterStrings = Page.Request.QueryString.GetValues("Delimiter") ?? new string[0];
            string[] headerRowStrings = Page.Request.QueryString.GetValues("HeaderRow") ?? new string[0];
            string[] xseriesparameterstrings = Page.Request.QueryString.GetValues("XSeriesParameter") ?? new string[0];
            string[] yseriesparameterstrings = Page.Request.QueryString.GetValues("YSeriesParameter") ?? new string[0];

            try
            {
                //Process delimited data file parse options
                this.ProcessQueryStringPairs(startRowStrings, DelimitedDataFileInfo.StartRowsSetter);
                this.ProcessQueryStringPairs(maxRowsString, DelimitedDataFileInfo.MaxRowsSetter);
                this.ProcessQueryStringPairs(delimiterStrings, DelimitedDataFileInfo.DelimiterSetter);
                this.ProcessQueryStringPairs(headerRowStrings, DelimitedDataFileInfo.HeaderRowSetter);

                //Process field selections
                this.ProcessQueryStringParameterChoice(xseriesparameterstrings, DelimitedDataFileInfo.XGraphSeriesSetter);
                this.ProcessQueryStringParameterChoice(yseriesparameterstrings, DelimitedDataFileInfo.YGraphSeriesSetter);

                //Load fields from data files user has requested
                this.LoadFields();
            }
            catch (Exception ex)
            {
                if (ex is FormatException || ex is OverflowException || ex is ArgumentNullException)
                {
                    HDCSharedWebFunctions.AddErrorLabel("Could not parse integer while processing file options", this.PlaceHolder_Messages);
                }
                throw;
            }
        }

        private DelimitedDataFileInfo LoadDataFileInfo(int dataFileID)
        {
            return this.LoadDataFileInfo(dataFileID, null);
        }

        private DelimitedDataFileInfo LoadDataFileInfo(int dataFileID, ExperimentDataFile dataFile)
        {
            DelimitedDataFileInfo dataFileInfo = null;

            if (!this.dataFilesDictionary.ContainsKey(dataFileID))
            {
                //Create a new entry

                //Lookup the data file if it wasn't passed already
                if (dataFile == null || dataFile.ID != dataFileID)
                {
                    dataFile = this.dbcontext.GetExperimentDataFileByID(dataFileID);
                }

                //Check it was a valid data file
                if (dataFile != null)
                {
                    dataFileInfo = new DelimitedDataFileInfo(dataFile);
                    this.dataFilesDictionary.Add(dataFileID, dataFileInfo);
                }
                else
                {
                    //This data file did not exist in the database, so return null
                    dataFileInfo = null;
                }
            }
            else
            {
                dataFileInfo = this.dataFilesDictionary[dataFileID];
            }

            return dataFileInfo; 
        }

        private void ProcessQueryStringPairs(string[] queryStrings, DelimitedDataFileInfo.DataFiles_RequestData_PropertySetter propertySetter)
        {
            foreach (string queryString in queryStrings)
            {
                int dataFileID;
                string value;
                bool parseSuccess = HDCSharedFunctions.TryParsePairString(queryString, out dataFileID, out value);

                if (parseSuccess)
                {
                    DelimitedDataFileInfo thisData = this.LoadDataFileInfo(dataFileID);
                    if (thisData != null)
                    {
                        propertySetter(thisData, value);
                    }
                }
            }
        }

        private void ProcessQueryStringParameterChoice(string[] queryStrings, DelimitedDataFileInfo.DataFiles_RequestData_GraphSeriesSetter parameterSetter)
        {
            foreach (string queryString in queryStrings)
            {
                string[] splitString;
                bool parseSuccess = HDCSharedFunctions.TrySplitString(queryString, ':', 3, out splitString);

                if (parseSuccess)
                {
                    int dataFileID;
                    string seriesname = splitString[1];
                    int fieldno;
                    if (int.TryParse(splitString[0], out dataFileID) && int.TryParse(splitString[2], out fieldno))
                    {
                        DelimitedDataFileInfo thisData = this.LoadDataFileInfo(dataFileID);
                        if (thisData != null)
                        {
                            parameterSetter(thisData, seriesname, fieldno);
                        }
                    }
                }
            }
        }

        private void RedirectToNewUrl()
        {
            List<DelimitedDataFileInfo> loadedDataFiles = this.LoadedDataFiles.ToList();
            List<DelimitedDataFileInfo> graphedDataFiles = this.GraphedDataFiles.ToList();

            string experimentids = (this._experimentSelectionControl.RequestedExperimentIDs.Count > 0 ? "&" + string.Join("&", this._experimentSelectionControl.RequestedExperimentIDs.Select(id => string.Format("ExperimentID={0}", id)).ToArray()) : "");
            string startrow = (loadedDataFiles.Count > 0 ? "&" + string.Join("&", loadedDataFiles.Select(rq => string.Format("StartRow={0}:{1}", rq.DataFile.ID, rq.StartRow)).ToArray()) : "");
            string maxrows = (loadedDataFiles.Count > 0 ? "&" + string.Join("&", loadedDataFiles.Select(rq => string.Format("MaxRows={0}:{1}", rq.DataFile.ID, rq.MaxRows)).ToArray()) : "");
            string delimiter = (loadedDataFiles.Count > 0 ? "&" + string.Join("&", loadedDataFiles.Select(rq => string.Format("Delimiter={0}:{1}", rq.DataFile.ID, rq.Delimiter)).ToArray()) : "");
            string headerrow = (loadedDataFiles.Count > 0 ? "&" + string.Join("&", loadedDataFiles.Select(rq => string.Format("HeaderRow={0}:{1}", rq.DataFile.ID, rq.HeaderRow)).ToArray()) : "");
            string xseriesparam = (graphedDataFiles.Count > 0 ? "&" + string.Join("&", graphedDataFiles.SelectMany(rq => rq.RequestedXFields.Select(f => string.Format("XSeriesParameter={0}:{1}:{2}", rq.DataFile.ID, f.Key, f.Value))).ToArray()) : "");
            string yseriesparam = (graphedDataFiles.Count > 0 ? "&" + string.Join("&", graphedDataFiles.SelectMany(rq => rq.RequestedYFields.Select(f => string.Format("YSeriesParameter={0}:{1}:{2}", rq.DataFile.ID, f.Key, f.Value))).ToArray()) : "");
            string charttitle = "&ChartTitle=" + HttpUtility.UrlEncode(this.txt_ChartTitle.Text);
            string xaxistitle = "&XAxisTitle=" + HttpUtility.UrlEncode(this.txt_XAxisTitle.Text);
            string yaxistitle = "&YAxisTitle=" + HttpUtility.UrlEncode(this.txt_YAxisTitle.Text);
            string redirectUrl = string.Format("{0}?{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}", this.Page.Request.Url.GetLeftPart(UriPartial.Path), experimentids, startrow, maxrows, delimiter, headerrow, xseriesparam, yseriesparam, charttitle, xaxistitle, yaxistitle);
            this.Response.Redirect(redirectUrl);
        }

        private void LoadDataFilesFromRequestedExperiments()
        {
            //Only load parameters if an experiment has been requested
            foreach (int expID in this._experimentSelectionControl.RequestedExperimentIDs)
            {
                IQueryable<ExperimentDataFile> experimentDataFiles = this.dbcontext.ExperimentDataFiles.Where(df => df.Experiment.ID == expID);

                foreach (ExperimentDataFile dataFile in experimentDataFiles)
                {
                    this.LoadDataFileInfo(dataFile.ID, dataFile);
                }
            }
        }

        private void LoadFields()
        {
            foreach (DelimitedDataFileInfo dataFileInfo in this.dataFilesDictionary.Values.Where(df => df.HasParseAttributes))
            {
                //Parse file to get field names
                using (FileParser_DelimitedFile parsedFile = new FileParser_DelimitedFile(dataFileInfo.DataFile.GetFullPath(), (char)dataFileInfo.Delimiter, (int)dataFileInfo.StartRow, (int)dataFileInfo.HeaderRow))
                {
                    //Save the field dictionary to the data file
                    dataFileInfo.Fields = parsedFile.FieldNames;

                    //Create a new field selection row for each field in the file
                    foreach (KeyValuePair<int, string> field in parsedFile.FieldNames)
                    {
                        XYGraphFieldSelection fieldInfo = new XYGraphFieldSelection();
                        fieldInfo.FieldNo = field.Key;
                        fieldInfo.DataItemID = dataFileInfo.DataFile.ID;
                        fieldInfo.DataItemName = string.Format("{0}:{1}:{2}", dataFileInfo.DataFile.Experiment.ID, dataFileInfo.DataFile.ID, field.Value);

                        //Try to find the X and Y series selections
                        KeyValuePair<string, int> matchingSeries;
                        bool seriesFound;

                        //Find X series selection by field key
                        seriesFound = DelimitedDataFileInfo.GetSeriesInfoByFieldNo(dataFileInfo.RequestedXFields, field.Key, out matchingSeries);
                        if (seriesFound)
                        {
                            fieldInfo.XSeries = matchingSeries.Key;
                        }

                        //Find Y series selection by field key
                        seriesFound = DelimitedDataFileInfo.GetSeriesInfoByFieldNo(dataFileInfo.RequestedYFields, field.Key, out matchingSeries);
                        if (seriesFound)
                        {
                            fieldInfo.YSeries = matchingSeries.Key;
                        }

                        //Add the new field selection
                        this.fieldSelectionList.Add(fieldInfo);
                    }
                }
            }
        }

        private void CreateGraph()
        {
            //Create graph data in the following format:
            //categories = dataset
            //series = parameter name

            try
            {
                //Set up graph options
                this.SetupGraph();

                //Generate the graph data
                GraphData_XY graphData = this.GetDataForGraph();

                //At this point, we have a graph type, categories, series data and possibly plot options an additional function string. Generate the javascript.
                string js = graphData.FormattedJavaScript;

                //Add script to page
                string ctxkey = "highchart" + this.Page.GetHashCode().ToString("x");
                if (!this.ClientScript.IsClientScriptBlockRegistered(this.GetType(), ctxkey))
                {
                    //We don't want to have the CSS cached so we append the date to the URL
                    this.ClientScript.RegisterClientScriptBlock(this.GetType(), ctxkey, js, false);
                }
            }
            catch (Exception e)
            {
                HDCSharedWebFunctions.AddErrorLabel("Error creating graph: " + e.Message, this.PlaceHolder_Messages);
            }
        }

        private void SetupGraph()
        {
            this.txt_ChartTitle.Text = Page.Request.QueryString["ChartTitle"] ?? "";
            this.txt_XAxisTitle.Text = Page.Request.QueryString["XAxisTitle"] ?? "";
            this.txt_YAxisTitle.Text = Page.Request.QueryString["YAxisTitle"] ?? "";

            //Lookup default values if no values were set
            DelimitedDataFileInfo graph1 = this.GraphedDataFiles.FirstOrDefault();
            if (graph1 != null)
            {
                if (string.IsNullOrEmpty(this.txt_XAxisTitle.Text))
                {
                    int fieldno = graph1.RequestedXFields.Values.First();
                    string fieldname = graph1.Fields[fieldno];
                    this.txt_XAxisTitle.Text = fieldname;
                }
                if (string.IsNullOrEmpty(this.txt_YAxisTitle.Text))
                {
                    int fieldno = graph1.RequestedYFields.Values.First();
                    string fieldname = graph1.Fields[fieldno];
                    this.txt_YAxisTitle.Text = fieldname;
                }
            }
        }

        private GraphData_XY GetDataForGraph()
        {
            //Create the graph data dictionary
            Dictionary<string, List<GraphData_XY.XYValues>> graphDataDictionary = new Dictionary<string, List<GraphData_XY.XYValues>>();

            foreach (DelimitedDataFileInfo dataFileRequest in this.dataFilesDictionary.Values.Where(rq => rq.HasParseAttributes && rq.HasRequestedFields))
            {
                //Add lists to graph data dictionary
                foreach (KeyValuePair<string, int> xField in dataFileRequest.RequestedXFields)
                {
                    if (!dataFileRequest.RequestedYFields.ContainsKey(xField.Key))
                    {
                        continue;
                    }

                    KeyValuePair<string, int> yField = dataFileRequest.RequestedYFields.Single(f => f.Key == xField.Key);
                    graphDataDictionary.Add(xField.Key, new List<GraphData_XY.XYValues>());
                }

                using (FileParser_DelimitedFile parsed_datafile = new FileParser_DelimitedFile(dataFileRequest.DataFile.GetFullPath(), (char)dataFileRequest.Delimiter, (int)dataFileRequest.StartRow, (int)dataFileRequest.HeaderRow))
                {
                    Dictionary<int, string> row_values;
                    while ((row_values = parsed_datafile.GetNextRowValues()) != null)
                    {
                        if (dataFileRequest.MaxRows > 0 && parsed_datafile.CurrentLineNoRel > dataFileRequest.MaxRows)
                        {
                            break;
                        }

                        foreach (KeyValuePair<string, int> xField in dataFileRequest.RequestedXFields)
                        {
                            if (!dataFileRequest.RequestedYFields.ContainsKey(xField.Key))
                            {
                                continue;
                            }
                            KeyValuePair<string, int> yField = dataFileRequest.RequestedYFields.Single(f => f.Key == xField.Key);

                            List<GraphData_XY.XYValues> series_data = graphDataDictionary[xField.Key];
                            series_data.Add(new GraphData_XY.XYValues(row_values[xField.Value], row_values[yField.Value]));
                        }
                    }
                }
            }

            GraphData_XY graphData = new GraphData_XY(graphDataDictionary, "scatter");

            graphData.ChartTitle = this.txt_ChartTitle.Text;
            graphData.XAxisTitle = this.txt_XAxisTitle.Text;
            graphData.YAxisTitle = this.txt_YAxisTitle.Text;

            return graphData;
        }

        #endregion Private methods


        #region Event handling

        
        protected void txt_ChartInfoChanged(object sender, EventArgs e)
        {
            this.redirect = true;
        }

        protected void ExperimentSelectionChanged(object sender, EventArgs e)
        {
            this.redirect = true;
        }

        private void ListView_DataFiles_ItemDataBound(object sender, ListViewItemEventArgs e)
        {
            ListViewDataItem lvDataItem = (ListViewDataItem)e.Item;
            DelimitedDataFileInfo dataFileInfo = (DelimitedDataFileInfo)lvDataItem.DataItem;
            TextBox txt_Delimiter = lvDataItem.FindControl("txt_Delimiter") as TextBox;
            txt_Delimiter.Text = System.Text.RegularExpressions.Regex.Escape(dataFileInfo.Delimiter.ToString());
        }

        protected void txt_DataFileOptions_TextChanged(object sender, EventArgs e)
        {
            TextBox textbox = (TextBox)sender;
            ListViewDataItem parentDataItem = (ListViewDataItem)textbox.NamingContainer;
            System.Web.UI.WebControls.ListView parentListView = (System.Web.UI.WebControls.ListView)(parentDataItem.BindingContainer);
            int listViewRowNo = parentDataItem.DataItemIndex;
            int datafileid = int.Parse(parentListView.DataKeys[listViewRowNo].Values[0].ToString());
            DelimitedDataFileInfo changedRequest = this.LoadDataFileInfo(datafileid);

            switch (textbox.ID)
            {
                case "txt_StartRow":
                    changedRequest.StartRow = HDCSharedFunctions.ConvertStringToNullableInt(textbox.Text) ?? -1;
                    break;
                case "txt_MaxRows":
                    changedRequest.MaxRows = HDCSharedFunctions.ConvertStringToNullableInt(textbox.Text) ?? -1;
                    break;
                case "txt_Delimiter":
                    char newdelim = HDCSharedFunctions.GetDelimiterFromTextBoxString(textbox.Text, ',');
                    changedRequest.Delimiter = newdelim;
                    break;
                case "txt_HeaderRow":
                    changedRequest.HeaderRow = HDCSharedFunctions.ConvertStringToNullableInt(textbox.Text) ?? -1;
                    break;
                case "txt_XParam":
                case "txt_YParam":
                    int fieldno = int.Parse(parentListView.DataKeys[listViewRowNo].Values[1].ToString());
                    if (textbox.ID == "txt_XParam")
                    {
                        DelimitedDataFileInfo.XGraphSeriesSetter(changedRequest, textbox.Text, fieldno);
                    }
                    else
                    {
                        DelimitedDataFileInfo.YGraphSeriesSetter(changedRequest, textbox.Text, fieldno);
                    }
                    break;
                default:
                    break;
            }

            this.redirect = true;
        }

        #endregion Event handling


        #region Data binding

        private void DatabindControls()
        {
            this.PopulateDataFileList();
            this.PopulateFieldSelectionList();
        }


        private void PopulateDataFileList()
        {
            this.ListView_DataFiles.DataSource = this.dataFilesDictionary.Values;
            this.ListView_DataFiles.DataBind();
        }

        private void PopulateFieldSelectionList()
        {
            this.ListView_Parameters.DataSource = this.fieldSelectionList;
            this.ListView_Parameters.DataBind();
        }

        #endregion Data binding

    }
}
